using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.Xna.Framework;
using Ultima.Xna.Diagnostics;
using Ultima.Xna.EventSystem;
using Ultima.Xna.Extensions;
using Ultima.Xna.Graphics;
using Ultima.Xna.Input;
using Ultima.Xna.Net;
using Ultima.Xna.SceneManagement;
using Ultima.Xna.Data;

namespace Ultima.Xna
{
    /// <summary>
    /// An abstract class that loads everything needed for the UltimaXna engine.
    /// </summary>
    public abstract class BaseEngine : Game
    {
        private GraphicsDeviceManager _graphics;
        private ISceneService _sceneService;
        private INetworkService _networkService;
        private ILoggingService _loggingService;
        private IInputService _inputService;
        private ICameraService _cameraService;
        private IEventService _eventService;

        /// <summary>
        /// Gets the scene service used by the engine to handle scene transitioning and setup
        /// </summary>
        public ISceneService SceneService
        {
            get { return _sceneService; }
        }

        /// <summary>
        /// Gets the networking service used by the engine to send and receive packet 
        /// information from the server
        /// </summary>
        public INetworkService NetworkService
        {
            get { return _networkService; }
        }

        /// <summary>
        /// Gets the logging service used to output logging to the debug log, sql, console, 
        /// or whatever logging appenders are setup in the log4net.config
        /// </summary>
        public ILoggingService LoggingService
        {
            get { return _loggingService; }
        }

        /// <summary>
        /// Gets the input service used to manage the input from the mouse and keyboard
        /// </summary>
        public IInputService InputService
        {
            get { return _inputService; }
        }

        /// <summary>
        /// Gets the camera service used by the engine, although it's immediate setup is used to render 
        /// Orthographically, it could be used to provide a different camera perspective.
        /// </summary>
        public ICameraService CameraService
        {
            get { return _cameraService; }
        }
        
        /// <summary>
        /// Gets the graphics device manager for the engine
        /// </summary>
        public GraphicsDeviceManager Graphics
        {
            get { return _graphics; }
        }

        public IEventService EventService
        {
            get { return _eventService; }
        }

        /// <summary>
        /// Creates an instance of BaseEngine
        /// </summary>
        public BaseEngine()
        {
            _graphics = new GraphicsDeviceManager(this);
            _graphics.PreferredBackBufferWidth = 800;
            _graphics.PreferredBackBufferHeight = 600;

            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Called after the Game and GraphicsDevice are created, but before LoadContent.
        /// </summary>
        protected override void Initialize()
        {
            //Create our services
            _loggingService = new Logger("Ultima.Xna");
            _eventService = new EventEngine(this);
            _networkService = new ClientNetwork();
            _inputService = new InputState(this);
            _cameraService = new CameraManager(this);

            //Create our Orthographic camera
            _cameraService.CurrentCamera = new OrthographicCamera(this);

            //Add the services to the service container for the game
            //so they are accessible by all our game components
            Services.AddService<ILoggingService>(_loggingService);
            Services.AddService<IEventService>(_eventService);
            Services.AddService<INetworkService>(_networkService);
            Services.AddService<IInputService>(_inputService);
            Services.AddService<ICameraService>(_cameraService);

            ASCIIText.Inititlize(this);

            //Setup all statically initializable objects.
            InvokeInitializers();

            //Create the scene service last since it requires a few of the 
            //above services to exist or it will crash
            _sceneService = new SceneManager(this);

            //Add it to the service container.
            Services.AddService<ISceneService>(_sceneService);

            //Add each of the components to the Games Component collection.
            //This ensures that the engine calls Update and Draw on each component for us.
            Components.Add(_inputService);
            Components.Add(_cameraService);
            Components.Add(_sceneService);

            base.Initialize();
        }

        /// <summary>
        /// Called when graphics resources need to be loaded. Override this
        /// method to load any game-specific graphics resources. Reference page contains
        /// links to related code samples.
        /// </summary>
        protected override void LoadContent()
        {
            base.LoadContent();
        }

        /// <summary>
        /// Invokes "public static void Initialize(Game game)" for each type that contains this function
        /// </summary>
        private void InvokeInitializers()
        {
            //Get all loaded assemblies
            Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();

            List<MethodInfo> invoke = new List<MethodInfo>();

            for (int a = 0; a < assemblies.Length; ++a)
            {
                //Get all types within that assembly
                Type[] types = assemblies[a].GetTypes();

                for (int i = 0; i < types.Length; ++i)
                {
                    //Find the method "public static void Intialize"
                    MethodInfo m = types[i].GetMethod("Initialize", BindingFlags.Static | BindingFlags.Public);

                    if (m != null)
                    {
                        //A valid methodinfo will contain 1 paramager of type Game.
                        bool valid = ((from arg in m.GetParameters()
                                       where arg.ParameterType == typeof(Game)
                                       && m.GetParameters().Length == 1
                                       select arg).Count() > 0);

                        //If its valid, add it to the invokation list.
                        if (valid)
                        {
                            invoke.Add(m);
                        }
                    }
                }
            }

            //Sort the invocation list by call order
            invoke.Sort(new CallPriorityComparer());

            //Invoke each function
            for (int i = 0; i < invoke.Count; i++)
            {
                invoke[i].Invoke(null, new object[] { this }); 
            }
        }
    }
}
